/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "FleetCtrlClient.hpp"


using namespace MaCI::MachineCtrl;
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
CFleetCtrlClient::CFleetCtrlClient(std::string aGIMnetAPAddress,
                                   unsigned int aGIMnetAPPort,
                                   std::string aFleetName,
                                   bool aContinuous):
  CSync(1,1), // Max One Mutex, Max one Condition
  CThread(1), // Max one Thread.
  iGIMnetAPAddress(aGIMnetAPAddress),
  iGIMnetAPPort(aGIMnetAPPort),
  iGIMnetName(""),
  iFleetName(aFleetName),
  iMachineNames(),
  iMachines(),
  iGIMIInstances(),
  iGIMI(NULL),
  iMCC(NULL),
  iDiscoveryTimeoutTime(5000),
  iDiscoveryLoopTime(5000),
  iContinuous(aContinuous),
  iThreadRunning(false)
{
  dPrint(1,"Constructing fleet '%s'",iFleetName.c_str());
 

  
}
//******************************************************************************
     
CFleetCtrlClient::~CFleetCtrlClient()
{

  DestructFleet();
}
//******************************************************************************
    
bool CFleetCtrlClient::ConnectFleet(){
  if(iGIMI == NULL){
    iGIMI = new gimi::GIMI();
    int r;
    if ( (r = iGIMI->connectToHubEx(iGIMnetAPAddress, iGIMnetAPPort, iGIMnetName) ) != GIMI_OK) {
      dPrint(1,"Failed to connect to GIMnet AccessPoint '%s:%d' with name '%s': '%s'",
             iGIMnetAPAddress.c_str(), 
             iGIMnetAPPort, 
             iGIMnetName.size()? iGIMnetName.c_str():"<anonymous>",
             gimi::getGimiErrorString(r).c_str());

      return false;
      
    } else {
      dPrint(2,"Succesfully connected to GIMnet at AccessPoint '%s:%d' with name '%s': '%s'",
           iGIMnetAPAddress.c_str(), 
             iGIMnetAPPort, 
             iGIMnetName.size()? iGIMnetName.c_str():"<anonymous>",
             gimi::getGimiErrorString(r).c_str());
      
      iMCC = new MaCI::MaCICtrl::CMaCICtrlClient(iGIMI,-1);

      if(!iContinuous){
        dPrint(1,"Not in continuos mode. Getting once all the machine from the fleet '%s'",iFleetName.c_str());
        UpdateFleet();
        dPrintLCGreen(1,"Succesfully connected fleet '%s'",iFleetName.c_str());

      }else{
        iThreadRunning = true;
        RunThread();
      }
      return true;
    }
    
    
  }else{
    dPrint(1,"Already connected to fleet");
    return false;
  }

}
//******************************************************************************

void CFleetCtrlClient::UpdateFleet()
{
  gimi::t_serviceList gimiServiceList;
 
  
  gimi::t_serviceList servicelist;
  iMCC->DiscoverMaCICtrlServices(servicelist, iDiscoveryTimeoutTime);
  
  MaCI::MaCICtrl::TServiceEntryArray entries;
  MaCI::MaCICtrl::TStringArray machinesInFleet;

  if (iMCC->GetServiceArray(entries, servicelist) == MaCI::KMaCIOK) {

    //Filter unique machine names
    FilterMachines(entries,machinesInFleet);

    ///Construct all machines
    for(EACH_IN_i(machinesInFleet)){
      ConstructMachine(*i);
    }

    //Remove all old machines that were not discovered in this run
    RemoveUndiscoveredMachines(machinesInFleet);
    
    
  }
}
//******************************************************************************

void CFleetCtrlClient::RemoveUndiscoveredMachines( MaCI::MaCICtrl::TStringArray aMachinesInFleet)
{
  MaCI::MaCICtrl::TStringArray machinesToRemove;
  Lock();
  for(EACH_IN_i(iMachineNames)){
    bool exists = false;
    for(EACH_IN_j(aMachinesInFleet)){
      if(j->compare(*i) == 0){
        exists = true;
      }
    }
    if(!exists){
      machinesToRemove.push_back(*i);
    }
  }

  for(EACH_IN_i(machinesToRemove)){
    TMachines::iterator iter = iMachines.find(*i);
    if(iter != iMachines.end()){
      delete (iter->second);
      iMachines.erase(iter);
    }else{
      dPrint(1,"WTF?? %s not found in machines",(*i).c_str());

    }

    std::map<std::string,gimi::GIMI *>::iterator iter2 =iGIMIInstances.find(*i);
    if(iter2 != iGIMIInstances.end()){
      delete(iter2->second);
      iGIMIInstances.erase(iter2);
    }else{
      dPrint(1,"WTF?? %s not found in GIMI Instances",(*i).c_str());

    }
    iMachineNames.erase(*i);
  }


  Unlock();
}
//******************************************************************************

void CFleetCtrlClient::FilterMachines( MaCI::MaCICtrl::TServiceEntryArray &aEntries,
                                       MaCI::MaCICtrl::TStringArray &aMaCIGroups){

  aMaCIGroups.clear();
  for(EACH_IN_i(aEntries)){
    std::string maciGroup = i->GetMaCISL().GetDeviceGroup();
    size_t found =  maciGroup.find(iFleetName);
    if(found == 0){ //the beginning

      //      dPrint(1,"macigroup %s iFleetname '%s' found %d:",maciGroup.c_str(),iFleetName.c_str(), found);
      bool alreadyAdded = false;
      for(EACH_IN_j(aMaCIGroups)){
        if(j->compare(maciGroup) == 0){
          //dPrint(1,"Already added");
          alreadyAdded = true;
            break;
        }
        
      }
      if(!alreadyAdded){
        //dPrint(1,"Not added.. adding");
        aMaCIGroups.push_back(maciGroup);

      }
      
    }else{
     
      
    }


  }


}
//******************************************************************************
TMachineNameList CFleetCtrlClient::GetMachineNames()
{
  Lock();
  
  TMachineNameList ret;
  for(EACH_IN_i(iMachineNames)){
    ret.push_back(*i);
  }
  Unlock();
  return ret;
}
//******************************************************************************

CMachineCtrlClient *CFleetCtrlClient::GetMachine(std::string aMachineName)
{
  dPrint(1,"Asking for machine %s",aMachineName.c_str());
  Lock();
  TMachines::iterator iter = iMachines.find(aMachineName);
  if(iter != iMachines.end()){
    CMachineCtrlClient *ret = iMachines[aMachineName];
    Unlock();
    return ret;

  }else{
    Unlock();
    return NULL;

  }

}
//******************************************************************************
TMachines CFleetCtrlClient::GetAllMachines()
{
  Lock();
  TMachines ret = iMachines;
  Unlock();
  return ret;

}


//******************************************************************************

unsigned int CFleetCtrlClient::GetFleetSize()
{
  Lock();
  unsigned int ret = iMachines.size();
  Unlock();
  return ret;

}
//******************************************************************************
bool CFleetCtrlClient::ConstructMachine(std::string aMaCIGroup){
  bool alreadyAdded = false;

  bool result = false;
  Lock();
  for(EACH_IN_i(iMachineNames)){
    if(i->compare(aMaCIGroup) == 0){

      
          alreadyAdded = true;
          break;
    }
    
  }

  if(!alreadyAdded){
    dPrint(1,"Found new Machine '%s' from fleet '%s'",aMaCIGroup.c_str(),iFleetName.c_str());
    iMachineNames.insert(aMaCIGroup);
    gimi::GIMI *gimi = new gimi::GIMI();
    int r;
    if ( (r = gimi->connectToHubEx(iGIMnetAPAddress, iGIMnetAPPort, iGIMnetName) ) != GIMI_OK) {
      dPrint(1,"Failed to connect to GIMnet AccessPoint '%s:%d' with name '%s': '%s'",
             iGIMnetAPAddress.c_str(), 
             iGIMnetAPPort, 
             iGIMnetName.size()? iGIMnetName.c_str():"<anonymous>",
             gimi::getGimiErrorString(r).c_str());


      
    } else {
      /*      dPrint(100,"Succesfully connected to GIMnet at AccessPoint '%s:%d' with name '%s': '%s'",
             iGIMnetAPAddress.c_str(), 
             iGIMnetAPPort, 
             iGIMnetName.size()? iGIMnetName.c_str():"<anonymous>",
             gimi::getGimiErrorString(r).c_str());*/

      MaCI::MachineCtrl::CMachineCtrlClient *machine = new MaCI::MachineCtrl::CMachineCtrlClient(gimi,aMaCIGroup);
      
      iGIMIInstances[aMaCIGroup] = gimi;
      iMachines[aMaCIGroup] = machine;
      dPrint(1,"Machine '%s'succesfully constructed",aMaCIGroup.c_str());
      result =  true;
    }
    
  

  }
  Unlock();
  return result;

}
//******************************************************************************

void CFleetCtrlClient::DestructFleet()
{
  Lock();
  for(EACH_IN_i(iMachines)){
    i->second->CloseClients();
    delete i->second;
  }

  for(EACH_IN_i(iGIMIInstances)){
    delete i->second;
  }

  iMachines.clear();
  iGIMIInstances.clear();
  iMachineNames.clear();
  Unlock();
}
//******************************************************************************

void CFleetCtrlClient::ChangeServiceDiscoveryTimeoutTime(unsigned int aNewTimeoutTime)
{
  iDiscoveryTimeoutTime = aNewTimeoutTime;
}
//******************************************************************************

void CFleetCtrlClient::ChangeContinuousServiceDiscoveryLoopTime(unsigned int aNewTimeoutTime)
{
  iDiscoveryLoopTime = aNewTimeoutTime;
}
//******************************************************************************
int CFleetCtrlClient::ThreadFunction(const int aThreadNumber){
  switch(aThreadNumber){

  case 0:
    return ContinuousFleetUpdateThread();
  default:
    return 0;

  }
 
}
//******************************************************************************
  
int CFleetCtrlClient::ContinuousFleetUpdateThread(){
  gim::time timebegin;
  gim::time timenow;
  while(iThreadRunning){
    
    timebegin.setToCurrent();
    UpdateFleet();
    timenow.setToCurrent();
    gim::time timeElapsed = timenow - timebegin;

    if(iDiscoveryTimeoutTime > (unsigned int)timeElapsed.getTimeMs()){
      unsigned int sleepingTime = iDiscoveryLoopTime - timeElapsed.getTimeMs();

      ownSleep_ms(sleepingTime);
    }

  }

  return 0;
}
